﻿using System;
using System.Reflection;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Windows.Input;
using Windows.UI.Xaml;

namespace MetroEventToCommand
{
    public class EventToCommand : FrameworkElement
    {
        #region RelayCommand

        public static readonly DependencyProperty CommandProperty =
            DependencyProperty.Register("Command", typeof(ICommand), typeof(EventToCommand),
                                        new PropertyMetadata(DependencyProperty.UnsetValue));

        public ICommand Command
        {
            get { return (ICommand)GetValue(CommandProperty); }
            set { SetValue(CommandProperty, value); }
        }

        #endregion

        #region RelayCommandParameter

        public static readonly DependencyProperty CommandParameterProperty =
            DependencyProperty.Register("CommandParameter", typeof(object), typeof(EventToCommand),
                                        new PropertyMetadata(DependencyProperty.UnsetValue));

        public object CommandParameter
        {
            get { return GetValue(CommandParameterProperty); }
            set { SetValue(CommandParameterProperty, value); }
        }

        #endregion

        #region RelayCommandEvent

        public static readonly DependencyProperty EventProperty =
            DependencyProperty.Register("Event", typeof(string), typeof(EventToCommand),
                                        new PropertyMetadata(DependencyProperty.UnsetValue));

        private FrameworkElement _element;

        public string Event
        {
            get { return (string)GetValue(EventProperty); }
            set { SetValue(EventProperty, value); }
        }

        #endregion

        public FrameworkElement Element
        {
            get { return _element; }
            set
            {
                _element = value;

                var eventinfo = _element.GetType().GetRuntimeEvent(Event);

                // if event not found, throw exception
                if (eventinfo == null)
                    throw new ArgumentException(string.Format("Event {0} not found on element {1}.", Event, Element.GetType().Name));

                //Get method to call when event raised
                var executemethodinfo = GetType().GetTypeInfo().GetDeclaredMethod("ExecuteCommand").CreateDelegate(eventinfo.EventHandlerType, this);

                // Register event
                WindowsRuntimeMarshal.AddEventHandler(
                    del => (EventRegistrationToken)eventinfo.AddMethod.Invoke(_element, new object[] { del }),
                    token => eventinfo.RemoveMethod.Invoke(_element, new object[] { token }), executemethodinfo);

            }
        }


        public void ExecuteCommand(object sender, object eventhandler)
        {
            var parsingok = false;

            // The typed parameter
            object typedparameter = null;

            // Set the DataContext to the element DataContext (Otherwise the binding returns NULL
            DataContext = Element.DataContext;

            //// Get the command
            //var command = Command;

            //Command is null, so don't do anything
            if (Command == null)
                return;

            // Get the parameter
            var parameter = CommandParameter;

            if (parameter != null)
            {
                // If there is a parameter, check if the command is a generic command
                if (Command.GetType().IsConstructedGenericType)
                {
                    // Get the generic argument type
                    var generictype = Command.GetType().GetTypeInfo().GenericTypeArguments[0];

                    //If the generic type is not string, and the parameter is string, let's cast
                    // If RelayCommandParameter is a direct input and not a binding, its value is of type string
                    // direct input is most probably a type (like int,etc...) so I'll try to cas to type
                    if ((generictype != typeof(string) && (CommandParameter is string)))
                    {
                        switch (generictype.Name)
                        {
                            case "Boolean":
                                bool boolresult;
                                parsingok = bool.TryParse(CommandParameter.ToString(), out boolresult);
                                if (parsingok)
                                {
                                    typedparameter = boolresult;
                                }
                                break;
                            case "Byte":
                                byte byteresult;
                                parsingok = byte.TryParse(CommandParameter.ToString(), out byteresult);
                                if (parsingok)
                                {
                                    typedparameter = byteresult;
                                }
                                break;
                            case "SByte":
                                sbyte sbyteresult;
                                parsingok = sbyte.TryParse(CommandParameter.ToString(), out sbyteresult);
                                if (parsingok)
                                {
                                    typedparameter = sbyteresult;
                                }
                                break;
                            case "Char":
                                char charresult;
                                parsingok = char.TryParse(CommandParameter.ToString(), out charresult);
                                if (parsingok)
                                {
                                    typedparameter = charresult;
                                }
                                break;
                            case "Decimal":
                                decimal decimalresult;
                                parsingok = decimal.TryParse(CommandParameter.ToString(), out decimalresult);
                                if (parsingok)
                                {
                                    typedparameter = decimalresult;
                                }
                                break;
                            case "Double":
                                double doubleresult;
                                parsingok = double.TryParse(CommandParameter.ToString(), out doubleresult);
                                if (parsingok)
                                {
                                    typedparameter = doubleresult;
                                }
                                break;
                            case "Single":
                                Single Singleresult;
                                parsingok = Single.TryParse(CommandParameter.ToString(), out Singleresult);
                                if (parsingok)
                                {
                                    typedparameter = Singleresult;
                                }
                                break;
                            case "Int32":
                                int intresult;
                                parsingok = int.TryParse(CommandParameter.ToString(), out intresult);
                                if (parsingok)
                                {
                                    typedparameter = intresult;
                                }
                                break;
                            case "UInt32":
                                UInt32 UInt32result;
                                parsingok = UInt32.TryParse(CommandParameter.ToString(), out UInt32result);
                                if (parsingok)
                                {
                                    typedparameter = UInt32result;
                                }
                                break;
                            case "Int64":
                                Int64 Int64result;
                                parsingok = Int64.TryParse(CommandParameter.ToString(), out Int64result);
                                if (parsingok)
                                {
                                    typedparameter = Int64result;
                                }
                                break;
                            case "UInt64":
                                UInt64 UInt64result;
                                parsingok = UInt64.TryParse(CommandParameter.ToString(), out UInt64result);
                                if (parsingok)
                                {
                                    typedparameter = UInt64result;
                                }
                                break;
                            case "Int16":
                                Int16 Int16result;
                                parsingok = Int16.TryParse(CommandParameter.ToString(), out Int16result);
                                if (parsingok)
                                {
                                    typedparameter = Int16result;
                                }
                                break;
                            case "UInt16":
                                UInt16 UInt16result;
                                parsingok = UInt16.TryParse(CommandParameter.ToString(), out UInt16result);
                                if (parsingok)
                                {
                                    typedparameter = UInt16result;
                                }
                                break;
                        }

                        if (!parsingok)
                        {
                            throw new ArgumentException(string.Format("Wrong parameter type. {0} expected, {1} received", generictype.Name, CommandParameter.GetType().Name));
                        }
                    }
                    else
                        typedparameter = CommandParameter;
                }
            }

            Command.Execute(typedparameter);

        }
    }
}
